using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace ASPItalia.ModelVirtualCasting.WPF.DragDrop
{
    /// <summary>
    /// Class that act as drag'n'drop target element manager for ItemsControls
    /// </summary>
    public class ItemsControlDragTargetAdvisor : IDropTargetAdvisor
    {
        private UIElement _targetUI;
        private Type _dataType;

        /// <summary>
        /// Get or set allowed datatype
        /// </summary>
        public Type DataType
        {
            get { return _dataType; }
            set { _dataType = value; }
        }

        /// <summary>
        /// Rappresent the target uielement to use for specific element
        /// </summary>
        public static readonly DependencyProperty DropTargetProperty = DependencyProperty.RegisterAttached("DropTarget", typeof(ItemsControl), typeof(ItemsControlDragTargetAdvisor));

        /// <summary>
        /// Method to set the drop target
        /// </summary>
        /// <param name="depObj">Dependency object</param>
        /// <param name="target">The target of the drop action</param>
        public static void SetDropTarget(DependencyObject depObj, ItemsControl target)
        {
            depObj.SetValue(DropTargetProperty, target);
        }

        /// <summary>
        /// Method to get the drop target
        /// </summary>
        /// <param name="depObj">Dependency object</param>
        /// <returns>The drop target</returns>
        public static ItemsControl GetDropTarget(DependencyObject depObj)
        {
            return (ItemsControl)depObj.GetValue(DropTargetProperty);
        }

        /// <summary>
        /// Get if drag'n'drop operation contains valid data
        /// </summary>
        /// <param name="obj">Clipboard dataobject</param>
        /// <returns></returns>
        public virtual bool IsValidDataObject(IDataObject obj)
        {
            if (obj.GetDataPresent(DataFormats.Serializable))
            {
                object item = obj.GetData(DataFormats.Serializable);
                return (this.DataType == null || item.GetType() == this.DataType);
            }
            return false;
        }

        /// <summary>
        /// Called when drop operation is finished. It adds item to target ItemsControl
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="dropPoint"></param>
        public virtual void OnDropCompleted(IDataObject obj, Point dropPoint)
        {
            object item = obj.GetData(DataFormats.Serializable);

            // Try to get target items control
            ItemsControl itemsControl = GetDropTarget(this.TargetUI);

            if (itemsControl == null)
            {
                if (this.TargetUI is ItemsControl)
                    itemsControl = (ItemsControl)this.TargetUI;
                else
                    itemsControl = ItemsControl.ItemsControlFromItemContainer(this.TargetUI);
            }

            AddItemToTarget(itemsControl, item, dropPoint);
        }

        /// <summary>
        /// Adds an item to the target
        /// </summary>
        /// <param name="itemsControl">The control</param>
        /// <param name="item">The item</param>
        /// <param name="dropPoint">Drop point</param>
        protected virtual void AddItemToTarget(ItemsControl itemsControl, object item, Point dropPoint)
        {
            // Does not add a new item if already exists
            if (itemsControl.Items.Contains(item)) return;

            // Add item to the ItemsControl collection
            if (itemsControl.ItemsSource != null)
            {
                IList list = itemsControl.ItemsSource as IList;
                if (list == null)
                    throw new InvalidOperationException("Cannot add item to ItemsSource");
                list.Add(item);
            }
            else
            {
                itemsControl.Items.Add(item);
            }
        }

        /// <summary>
        /// Get or set target drag'n'drop operation
        /// </summary>
        public UIElement TargetUI
        {
            get { return _targetUI; }
            set { _targetUI = value; }
        }

        /// <summary>
        /// Return the element rappresenting visual feedback during drag'n'drop operation
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public UIElement GetVisualFeedback(IDataObject obj)
        {
            UIElement uiItem = ExtractElement(obj);
            UIElement elt = VisualTreeHelper.GetChild(uiItem, 0) as UIElement;

            Rectangle rect = new Rectangle();
            rect.Width = elt.RenderSize.Width;
            rect.Height = elt.RenderSize.Height;
            rect.Opacity = 0.7d;

            VisualBrush brush = new VisualBrush(elt);
            //brush.Stretch = Stretch.None;
            //brush.ViewboxUnits = BrushMappingMode.Absolute;
            //brush.Viewbox = new Rect(new Point(0, 0), elt.RenderSize);
            rect.Fill = brush;
            rect.IsHitTestVisible = false;

            RenderOptions.SetCachingHint(rect, CachingHint.Cache);
            RenderOptions.SetCachingHint(brush, CachingHint.Cache);

            return rect;
        }

        /// <summary>
        /// Return the target drop mouse application point
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public Point GetOffsetPoint(IDataObject obj)
        {
            Point p = (Point)obj.GetData("point");
            return p;
        }

        private UIElement ExtractElement(IDataObject obj)
        {
            return (UIElement)obj.GetData(DataFormats.Locale);
        }
    }
}